import { clone, weightRandom } from './functions/util';
import { checkCondition, extractMaxTriggers } from './functions/condition';
import { getRate } from './functions/addition';
import { keyType } from './functions/define';

export class Talent {
    #talents: ITalentData;
    constructor() {
    }

    public initial(talents: ITalentData) {
        for (const id in talents) {
            const talent = talents[id];
            talent.id = Number(id);
            talent.grade = Number(talent.grade);
            talent.max_triggers = extractMaxTriggers(talent.condition);
            if (talent.replacement) {
                for (let key in talent.replacement) {
                    const obj: any = {};
                    for (let value of talent.replacement[key]) {
                        let values = `${value}`.split('*').map(a => Number(a));
                        obj[values[0] || 0] = Number(values[1]) || 1;
                    }
                    talent.replacement[key] = obj;
                }
            }
        }
    }

    count() {
        return Object.keys(this.#talents).length;
    }

    private check(talentId: any, property: any) {
        const { condition } = this.get(talentId);
        return checkCondition(property, condition ?? '');
    }

    get(talentId: number | string): ITalentDataItem {
        const talent = this.#talents[talentId];
        if (!talent) throw new Error(`[ERROR] No Talent[${talentId}]`);
        return clone(talent);
    }

    private information(talentId: number) {
        const { grade, name, description } = this.get(talentId)
        return { grade, name, description };
    }

    exclusive(talends: any, exclusiveId: number | string) {
        const { exclusive } = this.get(exclusiveId);
        if (!exclusive) return null;
        for (const talent of talends) {
            for (const e of exclusive) {
                if (talent == e) return talent;
            }
        }
        return null;
    }

    talentRandom(include: any, { times = 0, achievement = 0 } = {}) {
        const rate: any = { 1: 100, 2: 10, 3: 1, };
        const rateAddition: any = { 1: 1, 2: 1, 3: 1, };
        const timesRate: any = getRate('times', times);
        const achievementRate: any = getRate('achievement', achievement);

        for (const grade in timesRate)
            rateAddition[grade] += timesRate[grade] - 1;

        for (const grade in achievementRate)
            rateAddition[grade] += achievementRate[grade] - 1;

        for (const grade in rateAddition)
            rate[grade] *= rateAddition[grade];

        const randomGrade = () => {
            let randomNumber = Math.floor(Math.random() * 1000);
            if ((randomNumber -= rate[3]) < 0) return 3;
            if ((randomNumber -= rate[2]) < 0) return 2;
            if ((randomNumber -= rate[1]) < 0) return 1;
            return 0;
        }

        // 1000, 100, 10, 1
        const talentList: any = {};
        for (const talentId in this.#talents) {
            const { id, grade, name, description } = this.#talents[talentId];
            if (id == include) {
                include = { grade, name, description, id };
                continue;
            }
            if (!talentList[grade]) talentList[grade] = [{ grade, name, description, id }];
            else talentList[grade].push({ grade, name, description, id });
        }

        return new Array(10)
            .fill(1).map((v, i) => {
                if (!i && include) return include;
                let grade = randomGrade();
                while (talentList[grade].length == 0) grade--;
                const length = talentList[grade].length;

                const random = Math.floor(Math.random() * length) % length;
                return talentList[grade].splice(random, 1)[0];
            });
    }

    allocationAddition(talents: any) {
        if (Array.isArray(talents)) {
            let addition = 0;
            for (const talent of talents)
                addition += this.allocationAddition(talent);
            return addition;
        }
        return Number(this.get(talents).status) || 0;
    }

    do(talentId: number, property: any) {
        const { effect, condition, grade, name, description } = this.get(talentId);
        if (condition && !checkCondition(property, condition))
            return null;
        return { effect, grade, name, description };
    }

    replace(talents: any) {
        const getReplaceList = (talent: any, talents: any) => {
            const { replacement } = this.get(talent);
            if (!replacement) return null;
            const list = [];
            if (replacement.grade) {
                this.forEach(({ id, grade }: any) => {
                    if (!replacement.grade[grade]) return;
                    if (this.exclusive(talents, id)) return;
                    list.push([id, replacement.grade[grade]]);
                })
            }
            if (replacement.talent) {
                for (let id in replacement.talent as number[]) {
                    // @ts-ignore
                    id = Number(id);
                    if (this.exclusive(talents, id)) continue;
                    list.push([id, replacement.talent[id]]);
                }
            }
            return list;
        }

        const replace: any = (talent: any, talents: any) => {
            const replaceList = getReplaceList(talent, talents);
            if (!replaceList) return talent;
            const rand = weightRandom(replaceList);
            return replace(
                rand, talents.concat(rand)
            );
        }

        const newTalents = clone(talents);
        const result: any = {};
        for (const talent of talents) {
            const replaceId = replace(talent, newTalents);
            if (replaceId != talent) {
                result[talent] = replaceId;
                newTalents.push(replaceId);
            }
        }
        return result;
    }

    private forEach(callback: Function) {
        if (typeof callback != 'function') return;
        for (const id in this.#talents) {
            callback(clone(this.#talents[id]), id);
        }
    }

}

export type ITalentData = {
    [key in number | string]: ITalentDataItem;
}

export interface ITalentDataItem {
    id: number;
    name: string;
    grade: number;
    description: string;
    max_triggers: number;
    exclusive?: string[];
    status?: number;
    condition?: string;
    effect?: ITalentEffect;
    replacement?: ITalentReplacement;
}

export type ITalentEffect = {
    [key in keyType]: number
}

export interface ITalentReplacement {
    [key: string]: string | number[];
}